using System;
using System.Reflection;
using System.Collections;
using System.Text;

namespace Microsoft.Sdc.Tasks.Configuration.InstalledProducts
{
	/// <summary>
	/// Criteria is a Reverse Polish stack of operators:
	/// <list type="table">
	/// <item>
	///		<term>IBinaryComparisonChecker</term>
	///		<description>provides equality operator and any custom comparison implementation</description>
	/// </item>
	/// <item>
	///		<term>UnaryLogicalOperator</term>
	///		<description>Not or None</description>
	///	</item>
	///	<item>
	///		<term>BinaryLogicalOperator</term>
	///		<description>And, Or</description>
	///	</item>	
	///	</list>
	///	And values:
	///	<list type="table">
	///	<item>
	///		<term>PropertyInfo</term><description>property of an object that will be checked against</description>
	///		<term>Object</term><description>any constant value for the comparison arguments</description>
	///	</item>
	///	</list>	
	/// </summary>
	internal class Criteria
	{
		private Stack _criteria = new Stack();
		
		public int Count
		{
			get
			{
				return _criteria.Count;
			}
		}

		public void Push(IBinaryComparisonChecker checker)
		{
			_criteria.Push(checker);
		}

		public void Push(UnaryLogicalOperator op)
		{
			_criteria.Push(op);
		}

		public void Push(PropertyInfo prop)
		{
			_criteria.Push(prop);
		}

		public void Push(BinaryLogicalOperator op)
		{
			_criteria.Push(op);
		}

		public void Push(Object o)
		{
			_criteria.Push(o);
		}

		public override string ToString()
		{
			if (_criteria != null && _criteria.Count>0)
			{
				Stack stackCopy = (Stack)_criteria.Clone();
				try
				{					 
					return SummariseCriteriaStack(stackCopy);
				}
				catch (ApplicationException e)
				{
					return "Could not summarise criteria stack because of :"+e.Message+" stack is:"+_criteria;
				}
			}
			else
			{
				return "No criteria";
			}
		}

		/// <summary>
		/// Check through all the criterion to see if there is a mismatch otherwise
		/// return true.
		/// </summary>
        /// <param name="product">Object to match against</param>
		/// <returns>True if all criterion match</returns>
		public bool IsMatch(Object product)
		{
			// Make a copy of the stack so that it can be processed multiple times
			Stack stackCopy = (Stack)_criteria.Clone();
			// Final result of calculations should be a boolean
			bool result = (bool)EvaluateCriteriaStack(product,stackCopy);
			if (stackCopy.Count>0)
			{
				throw new ApplicationException("Not all of the criteria stack was processed. Could be missing a logical operator");
			}
			return result;
		}

		private object EvaluateCriteriaStack(Object product, Stack criteria)
		{
			object result=null;
			if (criteria.Count>0)
			{
				Object o = criteria.Pop();
				if (o is IBinaryComparisonChecker)
				{
					object lhs = EvaluateCriteriaStack(product,criteria);
					object rhs = EvaluateCriteriaStack(product,criteria);					
					// Get comparer to deal with these types
					result = ((IBinaryComparisonChecker)o).IsMatch(lhs,rhs);					
				}
				else if (o is PropertyInfo)
				{
					result = ((PropertyInfo)o).GetValue(product,null);
				}
				else if (o is UnaryLogicalOperator) // Usually Not or None
				{
					bool lhs = (bool)EvaluateCriteriaStack(product,criteria);
					if ((UnaryLogicalOperator)o == UnaryLogicalOperator.Not)
					{
						result = (! lhs);
					}
					else
					{
						result = lhs;
					}
				}
				else if (o is BinaryLogicalOperator) // And, Or
				{
					bool lhs = (bool)EvaluateCriteriaStack(product,criteria);
					bool rhs = (bool)EvaluateCriteriaStack(product,criteria);
					BinaryLogicalOperator bo = (BinaryLogicalOperator)o;
					switch (bo)
					{
						case BinaryLogicalOperator.And:
							result = (lhs && rhs);
							break;
						case BinaryLogicalOperator.Or: 
							result = (lhs || rhs);
							break;
						default: 
							throw new ApplicationException("Unknown logical operator:"+bo);
					}
				}
				else
				{
					// Must just be an object value for an argument (e.g. a string or a number)
					return o;
				}
			}
			return result;
		}

		private string SummariseCriteriaStack(Stack criteria)
		{
			StringBuilder result = new StringBuilder();
			if (criteria.Count>0)
			{
				Object o = criteria.Pop();
				if (o is IBinaryComparisonChecker)
				{
					result.Append("(");
					result.Append(SummariseCriteriaStack(criteria));
					result.Append(" ");
					result.Append(((IBinaryComparisonChecker)o).Operator);
					result.Append(" ");
					result.Append(SummariseCriteriaStack(criteria));
					result.Append(" [using ");
					result.Append(o.GetType().Name);					
					result.Append("]");
				}
				else if (o is PropertyInfo)
				{
					result.Append(((PropertyInfo)o).Name);
				}
				else if (o is UnaryLogicalOperator) // Usually Not or None
				{
					
					if (((UnaryLogicalOperator)o)!=UnaryLogicalOperator.None)
					{
						result.Append(o.ToString());
					}
					result.Append(" (");
					result.Append(SummariseCriteriaStack(criteria));
					result.Append(") ");
				}
				else if (o is BinaryLogicalOperator) // And, Or
				{
					result.Append("(");
					result.Append(SummariseCriteriaStack(criteria));
					result.Append(") ");
					result.Append(o);
					result.Append(" (");
					result.Append(SummariseCriteriaStack(criteria));
					result.Append(")");
				}
				else
				{
					// Must just be an object value for an argument (e.g. a string or a number)
					result.Append("'");
					result.Append(Convert.ToString(o));
					result.Append("'");
				}
			}
			return result.ToString();
		}

				
		public Criteria()
		{
		}
	}
}

